package com.ihr360.payroll.service.salarytask.impl;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.ihr360.authority.sharing.service.AuthUserResourceService;
import com.ihr360.authority.sharing.validator.GetOneValidator;
import com.ihr360.commons.data.util.Pair;
import com.ihr360.commons.exception.Ihr360Exception;
import com.ihr360.commons.lang.CollectionUtils;
import com.ihr360.commons.lang.DateUtils;
import com.ihr360.commons.lang.StringUtils;
import com.ihr360.commons.model.meta.table.DynamicCell;
import com.ihr360.commons.model.type.AccessPrivilege;
import com.ihr360.commons.model.type.FieldSource;
import com.ihr360.commons.model.type.SalaryFieldValueType;
import com.ihr360.commons.request.Ihr360SearchBody;
import com.ihr360.commons.specification.Ihr360Specification;
import com.ihr360.entity.ApplicationApprovalEntity;
import com.ihr360.entity.BasicStaffInfoEntity;
import com.ihr360.excel.ExcelUtil;
import com.ihr360.excel.constants.ExcelDefaultConfig;
import com.ihr360.excel.entity.ExcelDataCellEntity;
import com.ihr360.excel.entity.ExportParams;
import com.ihr360.excel.utils.SpreadSheetConvertResultUtil;
import com.ihr360.excel.vo.CustomSpreadSheetValidateResult;
import com.ihr360.excel.vo.ImportRequest;
import com.ihr360.excel.vo.SpreadSheetCell;
import com.ihr360.excel.vo.SpreadSheetRow;
import com.ihr360.excel.vo.SpreadSheetValidatorError;
import com.ihr360.excel.vo.SpreadSheetValidatorRequest;
import com.ihr360.job.core.repository.JobExecutionAlreadyRunningException;
import com.ihr360.payroll.dao.ActivePayPeriodRepository;
import com.ihr360.payroll.dao.HistoryPayPeriodRepository;
import com.ihr360.payroll.dao.PayPeriodTermRepository;
import com.ihr360.payroll.dao.SalaryPlanRepository;
import com.ihr360.payroll.dao.StaffPayPeriodTermCalcContextRepository;
import com.ihr360.payroll.dao.StaffPayPeriodTermSalaryRepository;
import com.ihr360.payroll.entity.task.PayPeriodEntity;
import com.ihr360.payroll.entity.task.PayPeriodTermEntity;
import com.ihr360.payroll.model.dto.salary.embeddable.RefServiceStatistics;
import com.ihr360.payroll.model.dto.salary.meta.PayrollConfig;
import com.ihr360.payroll.model.dto.salary.plan.SalaryPlan;
import com.ihr360.payroll.model.dto.salary.report.HistoryPayPeriod;
import com.ihr360.payroll.model.dto.salary.task.period.ActivePayPeriod;
import com.ihr360.payroll.model.dto.salary.task.period.PayPeriodTerm;
import com.ihr360.payroll.model.dto.salary.task.period.StaffPayPeriodTermCalcContext;
import com.ihr360.payroll.model.dto.salary.task.period.StaffPayPeriodTermSalary;
import com.ihr360.payroll.model.dto.salary.task.period.embeddable.StaffSalaryCalcContext;
import com.ihr360.payroll.model.dto.salary.task.period.embeddable.StaffSalarySnapshot;
import com.ihr360.payroll.model.dto.salary.task.period.embeddable.StaffSalarySnapshotListWrapper;
import com.ihr360.payroll.model.dto.salary.task.period.embeddable.StaffSalaryTermId;
import com.ihr360.payroll.model.vo.SalaryDataEntity;
import com.ihr360.payroll.model.vo.SalaryFieldEntity;
import com.ihr360.payroll.model.vo.SalaryFieldVo;
import com.ihr360.payroll.model.vo.StaffPayPeriodTermSalaryVo;
import com.ihr360.payroll.response.PayrollApprovalResponse;
import com.ihr360.payroll.service.payperiod.StaffPayPeriodTermSalaryService;
import com.ihr360.payroll.service.salaryplan.SalaryPlanService;
import com.ihr360.payroll.service.salaryplan.SalaryPlanStaffService;
import com.ihr360.payroll.service.salaryreport.HistoryPayPeriodService;
import com.ihr360.payroll.service.salarytask.SalaryTaskService;
import com.ihr360.payroll.service.salarytask.convertor.PayPeriodConvertor;
import com.ihr360.payroll.service.salarytask.validator.PayPeriodAuthValidator;
import com.ihr360.payroll.service.salarytask.validator.PayPeriodTermAuthValidator;
import com.ihr360.payroll.service.salarytasklet.SalaryTaskletService;
import com.ihr360.payroll.type.PeriodState;
import com.ihr360.payroll.type.StaffRefStatus;
import com.ihr360.shared.basicstaffinfo.dto.SyncedBasicStaffInfo;
import com.ihr360.shared.basicstaffinfo.service.BasicStaffInfoService;
import com.ihr360.tasklet.JobExecutionOverview;
import com.ihr360.validation.ValidationUtils;
import com.ihr360.validation.annonation.EnableValidation;
import com.ihr360.validation.annonation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.ServletOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 薪资任务相关服务Root
 */
@Service
public class SalaryTaskServiceImpl implements SalaryTaskService {
    private final Logger logger = LoggerFactory.getLogger(this.getClass().getName());
    @Autowired
    ActivePayPeriodRepository payPeriodRepository;

    @Autowired
    SalaryPlanRepository salaryPlanRepository;

    @Autowired
    SalaryPlanService salaryPlanService;

    @Autowired
    PayPeriodTermRepository payPeriodTermRepository;

    @Autowired
    StaffPayPeriodTermSalaryRepository staffPayPeriodTermSalaryRepository;

    @Autowired
    StaffPayPeriodTermSalaryService staffPayPeriodTermSalaryService;

    @Autowired
    PayPeriodConvertor payPeriodConvertor;

    @Autowired
    SalaryPlanStaffService salaryPlanStaffService;

    @Autowired
    SalaryTaskletService salaryTaskletService;

    @Autowired
    private HistoryPayPeriodService historyPayPeriodService;

    @Autowired
    private AuthUserResourceService authUserResourceService;

    @Autowired
    private StaffPayPeriodTermCalcContextRepository calcContextRepository;

    @Autowired
    private HistoryPayPeriodRepository historyPayPeriodRepository;

    @Autowired
    private GetOneValidator getOneValidator;

    @Override
    public List<SalaryPlan> getActivePlanIds(String companyId, Long planId) {
        List<ActivePayPeriod> activePayPeriods = payPeriodRepository.findAllByCompanyIdAndSalaryPlanId(companyId, planId);
        if (CollectionUtils.isEmpty(activePayPeriods)) {
            return Collections.emptyList();
        }
        return activePayPeriods.stream().map(ActivePayPeriod::getSalaryPlan).collect(Collectors.toList());
    }

    @Override
    public List<SalaryPlan> getActiveAttendancePlanIds(String companyId, Long attendancePlanId) {
        List<ActivePayPeriod> activePayPeriods = payPeriodRepository.findAllByCompanyIdAndSalaryPlanAttendancePlanId(companyId, attendancePlanId);
        if (CollectionUtils.isEmpty(activePayPeriods)) {
            return Collections.emptyList();
        }
        return activePayPeriods.stream().map(ActivePayPeriod::getSalaryPlan).collect(Collectors.toList());
    }

    @Override
    public List<SalaryPlan> getActivePayrollConfigIds(String companyId, Long payrollConfigId) {
        List<ActivePayPeriod> activePayPeriods = payPeriodRepository.findAllByCompanyIdAndSalaryPlanPayrollConfigId(companyId, payrollConfigId);
        if (CollectionUtils.isEmpty(activePayPeriods)) {
            return Collections.emptyList();
        }
        return activePayPeriods.stream().map(ActivePayPeriod::getSalaryPlan).collect(Collectors.toList());
    }

    /**
     * 是否应该考虑只获取自己创建的方案，而不是公司的所有方案
     *
     * @return
     */
    @Override
    public List<PayPeriodEntity> getPayPeriods(String companyId) {

        //1.获取公司薪资方案
        List<SalaryPlan> salaryPlans = salaryPlanService.getAllInCompanyContainSystemTemplate();
        if (CollectionUtils.isEmpty(salaryPlans)) {
            return Collections.emptyList();
        }

        //2.根据薪资方案，获取对应账期数据
        List<ActivePayPeriod> activePayPeriods = payPeriodRepository.findAllByCompanyIdAndSalaryPlanIdIn(companyId,
                salaryPlans.stream().map(SalaryPlan::getId).collect(Collectors.toList()));
        List<PayPeriodEntity> payPeriodEntities = payPeriodConvertor.convertDtosToEntities(activePayPeriods);

        //3.如果薪资方案没有对应的账期，则新建一个临时账期给前端使用(方案状态enabled==true才新建)
        List<Long> existedPlanIds = activePayPeriods.stream().map(item -> item.getSalaryPlan().getId()).collect(Collectors.toList());
        salaryPlans.removeIf(item -> existedPlanIds.contains(item.getId()));
        if (CollectionUtils.isNotEmpty(salaryPlans)) {
            if (CollectionUtils.isEmpty(payPeriodEntities)) {
                payPeriodEntities = Lists.newArrayList();
            }
            for (SalaryPlan salaryPlan : salaryPlans) {
                if (isSalaryPlanCanOpen(salaryPlan)) {
                    PayPeriodEntity payPeriodEntity = generateSimplePayPeriodEntity(salaryPlan);
                    payPeriodEntity.setNeedCopyCanStart(salaryPlan.isSystemTemplate());
                    if (salaryPlan.isSystemTemplate()) {
                        payPeriodEntities.add(0, payPeriodEntity);
                    } else {
                        payPeriodEntities.add(payPeriodEntity);
                    }
                }
            }
        }
        return payPeriodEntities;
    }

    /**
     * 薪资方案是否允许开启
     *
     * @param salaryPlan
     * @return
     */
    private boolean isSalaryPlanCanOpen(SalaryPlan salaryPlan) {
        AccessPrivilege accessPrivilege = salaryPlan.getResourceAccess().getAccessPrivilege();
        return salaryPlan.isEnabled()
                && (accessPrivilege == null
                || accessPrivilege == AccessPrivilege.OWNER
                || accessPrivilege == AccessPrivilege.EDITOR);
    }

    private PayPeriodEntity generateSimplePayPeriodEntity(SalaryPlan plan) {
        return PayPeriodEntity.builder()
                .planId(plan.getId())
                .planName(plan.getName())
                .planPrivilege(plan.getResourceAccess().getAccessPrivilege())
                .planMonthCycle(plan.isMonthCycle())
                .build();
    }

    @Override
    public PayPeriodEntity getPayPeriod(Long periodId) {
        ActivePayPeriod activePayPeriod = getActivePayPeriod(periodId);
        PayPeriodEntity entity = payPeriodConvertor.convertDtoToEntity(activePayPeriod);
        Pair<Integer, Integer> privilegePair = authUserResourceService.queryUserResourcePrivilege(SalaryPlan.class,
                activePayPeriod.getSalaryPlan().getId().toString());
        if (privilegePair != null) {
            entity.setPlanPrivilege(AccessPrivilege.getAccessPrivilege(privilegePair.getFirst()));
        }
        return entity;
    }

    @Override
    public PayPeriodEntity getNewPayPeriod(String companyId, Long salaryPlanId) {
        int payPeriodCount = payPeriodRepository.countByCompanyIdAndSalaryPlanId(companyId, salaryPlanId);
        ValidationUtils.rejectGt(payPeriodCount, 0, null, "该薪资方案的台账已经存在，请刷新后重试");
        SalaryPlan salaryPlan = salaryPlanRepository.findOne(salaryPlanId);
        ValidationUtils.rejectIfEmpty(salaryPlan, null, "薪资方案不存在，请刷新后重试");
        return generateNewPayPeriodEntity(salaryPlan);
    }

    private PayPeriodEntity generateNewPayPeriodEntity(SalaryPlan plan) {
        if (plan.isMonthCycle()) {
            //单批次
            Integer calculateCycleMonth = plan.getCalculateCycleMonth();
            ValidationUtils.rejectIfEmpty(calculateCycleMonth, null, "薪资周期未设置");
            Integer calculateCycleDay = plan.getCalculateCycleDay();
            ValidationUtils.rejectIfEmpty(calculateCycleDay, null, "薪资周期未设置");
            List<Date> realSalaryCycleDate = getRealCycleDate(calculateCycleMonth, calculateCycleDay);

            Integer attendanceCycleMonth = plan.getAttendanceCycleMonth();
            ValidationUtils.rejectIfEmpty(attendanceCycleMonth, null, "考勤周期未设置");
            Integer attendanceCycleDay = plan.getAttendanceCycleDay();
            ValidationUtils.rejectIfEmpty(attendanceCycleDay, null, "考勤周期未设置");
            List<Date> realAttendanceCycleDate = getRealCycleDate(attendanceCycleMonth, attendanceCycleDay);

            return PayPeriodEntity.builder()
                    .planId(plan.getId())
                    .planName(plan.getName())
                    .planMonthCycle(plan.isMonthCycle())
                    .startOn(realSalaryCycleDate.get(0))
                    .endOn(realSalaryCycleDate.get(1))
                    .attendanceStartOn(realAttendanceCycleDate.get(0))
                    .attendanceEndOn(realAttendanceCycleDate.get(1))
                    .terms(Lists.newArrayList(new PayPeriodTermEntity()))
                    .build();
        } else {
            //多批次
            return PayPeriodEntity.builder()
                    .planId(plan.getId())
                    .planName(plan.getName())
                    .planMonthCycle(plan.isMonthCycle())
                    .build();
        }
    }

    /**
     * 根据薪资方案设定周期计算出具体的薪资时间
     *
     * @param cycleMonth
     * @param cycleDay
     * @return
     */
    private List<Date> getRealCycleDate(int cycleMonth, int cycleDay) {
        Calendar calendar = Calendar.getInstance();
        int currYear = calendar.get(Calendar.YEAR);
        int currMonth = calendar.get(Calendar.MONTH) + 1;

        int realStartYear = 0;
        int realStartMonth = 0;
        int realStartDay;

        int realEndYear;
        int realEndMonth;
        int realEndDay;
        if (cycleMonth == SalaryPlan.CycleMonth.BEFORE_LAST) {
            //上上月
            realStartMonth = currMonth - 2;
            if (realStartMonth <= 0) {
                realStartMonth = realStartMonth + 12;
                realStartYear = currYear - 1;
            } else {
                realStartYear = currYear;
            }
        } else if (cycleMonth == SalaryPlan.CycleMonth.LAST) {
            //上月
            realStartMonth = currMonth - 1;
            if (realStartMonth <= 0) {
                realStartMonth = realStartMonth + 12;
                realStartYear = currYear - 1;
            } else {
                realStartYear = currYear;
            }
        } else if (cycleMonth == SalaryPlan.CycleMonth.THIS) {
            //本月
            realStartMonth = currMonth;
            realStartYear = currYear;
        } else if (cycleMonth == SalaryPlan.CycleMonth.NEXT) {
            //下月
            realStartMonth = currMonth + 1;
            if (realStartMonth > 12) {
                realStartMonth = realStartMonth - 12;
                realStartYear = currYear + 1;
            } else {
                realStartYear = currYear;
            }
        } else {
            ValidationUtils.reject(null, "周期设定值无效");
        }

        realStartDay = Math.min(cycleDay, DateUtils.getMaxDayOfMonth(realStartYear, realStartMonth));

        if (cycleDay == 1) {
            realEndYear = realStartYear;
            realEndMonth = realStartMonth;
            realEndDay = DateUtils.getMaxDayOfMonth(realEndYear, realEndMonth);
        } else {
            if (realStartMonth == 12) {
                realEndYear = realStartYear + 1;
                realEndMonth = 1;
            } else {
                realEndYear = realStartYear;
                realEndMonth = realStartMonth + 1;
            }
            realEndDay = Math.min(cycleDay, DateUtils.getMaxDayOfMonth(realEndYear, realEndMonth)) - 1;
        }
        List<Date> startAndEndDate = Lists.newArrayListWithCapacity(2);
        calendar.set(realStartYear, realStartMonth - 1, realStartDay);
        Date start = calendar.getTime();
        startAndEndDate.add(start);

        calendar.set(realEndYear, realEndMonth - 1, realEndDay);
        Date end = calendar.getTime();
        startAndEndDate.add(end);
        return startAndEndDate;
    }

    private ActivePayPeriod getActivePayPeriod(Long payPeriodId) {
        ActivePayPeriod period = payPeriodRepository.findOne(payPeriodId);
        ValidationUtils.rejectIfEmpty(period, null, "账期数据不存在，请刷新后重试");
        return period;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @EnableValidation
    public void revoke(@Valid(validator = PayPeriodAuthValidator.class) Long payPeriodId) {
        ActivePayPeriod payPeriod = getActivePayPeriod(payPeriodId);
        List<PayPeriodTerm> terms = payPeriod.getTerms();
        if (CollectionUtils.isNotEmpty(terms)) {
            List<Long> idList = terms.stream().map(PayPeriodTerm::getId).collect(Collectors.toList());
            staffPayPeriodTermSalaryRepository.deleteAllByIdTermIdIn(idList);
            //解除关联关系
            terms.forEach(term -> term.setPayPeriod(null));
            payPeriodTermRepository.deleteByIdIn(idList);
        }
        salaryPlanStaffService.resetSalaryPlanStaffStatus(payPeriod.getSalaryPlan().getId());

        //会自动级联删除相关数据
        payPeriodRepository.delete(payPeriodId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PayPeriodEntity setupPayPeriod(Long payperiodId, PayPeriodEntity payPeriod) {
        ValidationUtils.rejectIfEmpty(payPeriod.getPlanId(), null, "薪资方案未设置");
        if (payPeriod.getId() != null) {
            //不是新创建的账期，考虑是否需要对批次信息进行加工
            getOneValidator.validate(payPeriod.getPlanId(), SalaryPlan.class);
            return updatePayPeriod(payPeriod);
        } else {
            //创建账期
            return initPayPeriod(payPeriod);
        }
    }

    @Override
    public PayPeriodTermEntity getPayPeriodTerm(@Valid(validator = PayPeriodTermAuthValidator.class)Long payPeriodTermId) {
        PayPeriodTerm payPeriodTerm = payPeriodTermRepository.findOne(payPeriodTermId);
        return payPeriodConvertor.convertPayPeriodTermToEntity(payPeriodTerm);
    }

    /**
     * 初始化一个账期
     *
     * @param payPeriod
     * @return
     */
    private PayPeriodEntity initPayPeriod(PayPeriodEntity payPeriod) {
        SalaryPlan salaryPlan = validSalaryPlan(payPeriod);
        final ActivePayPeriod period = payPeriodConvertor.convertEntityToDto(payPeriod);
        setPayPeriodYearMonth(period, salaryPlan);
        checkPayPeriodIsExist(payPeriod.getPlanId(), period);
        List<PayPeriodTerm> terms = period.getTerms();
        if (period.getCurrentTerm() == null) {
            period.setCurrentTerm(terms.get(0));
        }
        if (period.getLastTerm() == null) {
            period.setLastTerm(terms.get(terms.size() - 1));
        }
        //重新设定后，重置账期状态为创建完成后的初始状态
        period.setState(PeriodState.STAFF_CHECKING);

        ActivePayPeriod resultDto = payPeriodRepository.save(period);
        return payPeriodConvertor.convertDtoToEntity(resultDto);
    }

    private void setPayPeriodYearMonth(ActivePayPeriod period, SalaryPlan salaryPlan) {
        Integer salaryMonth = salaryPlan.getSalaryMonth();

        ValidationUtils.rejectIfEmpty(salaryMonth, null, "薪资方案未设置薪资所属月，请设置");
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(period.getStartOn());
        calendar.add(Calendar.MONTH, salaryMonth);

        period.setYear(calendar.get(Calendar.YEAR));
        period.setMonth(calendar.get(Calendar.MONTH) + 1);
    }

    /**
     * 根据方案Id验证方案是否存在
     * notes: 如果方案为模板则克隆一个新的方案
     *
     * @param payPeriod
     */
    private SalaryPlan validSalaryPlan(PayPeriodEntity payPeriod) {
        SalaryPlan salaryPlan = salaryPlanService.getOneContainSystemTemplate(payPeriod.getPlanId());
        if (salaryPlan == null) {
            throw new Ihr360Exception("", "方案获取失败.");
        }
        if (salaryPlan.isSystemTemplate()) {
            // 模板方案不能直接开启帐期，需要转化为私有才可开启
            salaryPlan = salaryPlanService.cloneSalaryPlan(salaryPlan.getId());
            payPeriod.setPlanId(salaryPlan.getId());
        }
        return salaryPlan;
    }

    /**
     * 更新账期
     * 1、如果账期批次重要数据（薪资周期、考勤周期等）发生改变，则重置整个账期
     * 2、反之，则直接保存
     *
     * @param payPeriod
     * @return
     */
    private PayPeriodEntity updatePayPeriod(PayPeriodEntity payPeriod) {

        List<PayPeriodTermEntity> payPeriodTermEntities = payPeriod.getTerms();
        ActivePayPeriod activePayPeriod = getActivePayPeriod(payPeriod.getId());

        List<PayPeriodTerm> terms = activePayPeriod.getTerms();
        if (hasPeriodTermsChanged(payPeriodTermEntities, activePayPeriod)) {
            List<Long> termIds = terms.stream().map(PayPeriodTerm::getId).collect(Collectors.toList());
            staffPayPeriodTermSalaryRepository.deleteAllByIdTermIdIn(termIds);
            payPeriodTermRepository.delete(activePayPeriod.getTerms());
            return initPayPeriod(payPeriod);
        } else {
            Map<Long, PayPeriodTermEntity> termEntityMap = payPeriodTermEntities.stream().
                    collect(Collectors.toMap(PayPeriodTermEntity::getId, self -> self));
            terms.forEach(item -> {
                PayPeriodTermEntity termEntity = termEntityMap.get(item.getId());
                item.setBenefitsTerm(termEntity.isBenefitsTerm());
                item.setState(termEntity.getState());
                item.setReportState(termEntity.getReportState());
                item.setPayslipState(termEntity.getPayslipState());
            });

            activePayPeriod.setState(payPeriod.getState());
            ActivePayPeriod updated = payPeriodRepository.save(activePayPeriod);
            return payPeriodConvertor.convertDtoToEntity(updated);
        }
    }

    private boolean hasPeriodTermsChanged(List<PayPeriodTermEntity> payPeriodTermEntities, ActivePayPeriod activePayPeriod) {
        if (payPeriodTermEntities.size() != activePayPeriod.getTerms().size()) {
            return true;
        }
        List<PayPeriodTerm> terms = activePayPeriod.getTerms();
        Map<Long, PayPeriodTermEntity> termEntityMap = payPeriodTermEntities.stream().
                collect(Collectors.toMap(PayPeriodTermEntity::getId, self -> self));
        for (PayPeriodTerm term : terms) {
            PayPeriodTermEntity termEntity = termEntityMap.get(term.getId());
            ValidationUtils.rejectIfEmpty(termEntity, null, "要更新的账期批次不存在，请刷新后重试");
            if (!isSamePeriodTerm(term, termEntity)) {
                return true;
            }
        }
        return false;
    }

    private boolean isSamePeriodTerm(PayPeriodTerm term, PayPeriodTermEntity termEntity) {
        return DateUtils.isSameDay(term.getStartOn(), termEntity.getStartOn())
                && DateUtils.isSameDay(term.getEndOn(), termEntity.getEndOn())
                && DateUtils.isSameDay(term.getAttendanceStartOn(), termEntity.getAttendanceStartOn())
                && DateUtils.isSameDay(term.getAttendanceEndOn(), termEntity.getAttendanceEndOn())
                && term.getWorkdays() == termEntity.getWorkdays();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @EnableValidation
    public void addStaff2PayPeriod(@Valid(validator = PayPeriodAuthValidator.class) Long periodId, List<String> staffIdList) {
        ActivePayPeriod period = getActivePayPeriod(periodId);
        salaryPlanStaffService.dummyAddStaff2Plan(period.getSalaryPlan().getId(), staffIdList);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @EnableValidation
    public void deleteStaff2PayPeriod(@Valid(validator = PayPeriodAuthValidator.class) Long periodId, List<String> staffIdList) {
        ActivePayPeriod period = getActivePayPeriod(periodId);
        salaryPlanStaffService.dummyDeleteSalaryPlanStaff(period.getSalaryPlan().getId(), staffIdList);
        //账期未首次核算之前，不存在员工薪资数据，无需删除
        if (period.getState() == PeriodState.STAFF_CHECKING) {
            return;
        }
        //删除员工的薪资数据
        staffPayPeriodTermSalaryRepository.deleteAllByIdTermIdAndIdStaffIdIn(period.getCurrentTerm().getId(), staffIdList);
    }

    @Override
    public JobExecutionOverview launchData( Long periodId) {
        ActivePayPeriod period = getActivePayPeriod(periodId);
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(period.getStartOn());
        JobExecutionOverview jobExecutionOverview = null;
        try {
            jobExecutionOverview = salaryTaskletService.launchPreDataJob(period.getSalaryPlan().getId(), periodId,
                    period.getCurrentTerm().getId(), null, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1,
                    period.getCurrentTerm().getTaskExecutionSequence());
        } catch (JobExecutionAlreadyRunningException e) {
            ValidationUtils.reject(null, "数据更新任务正在执行中，不能反复执行");
        }

        ValidationUtils.rejectIfEmpty(jobExecutionOverview, null, "任务未能执行，请刷新后重试");
        PayPeriodTerm.JobExecutionIdListWrapper jobExecutionIds = new PayPeriodTerm.JobExecutionIdListWrapper(Lists.newArrayList());
        jobExecutionIds.getJobExecutionIds().add(jobExecutionOverview.getExecutionId());
        period.getCurrentTerm().setJobExecutionIds(jobExecutionIds);

        period.setState(PeriodState.CALCULATING);
        period.getCurrentTerm().setState(PeriodState.CALCULATING);

        ((SalaryTaskService) AopContext.currentProxy()).saveDataInNewTransaction(period);

        return jobExecutionOverview;
    }

/*    @Override
    public JobExecutionOverview firstCalculate(Long periodId) {
        ActivePayPeriod period = getActivePayPeriod(periodId);
        if (period.getState() != PeriodState.STAFF_CHECKING) {
            return null;
        }
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(period.getStartOn());
        JobExecutionOverview jobExecutionOverview = null;
        try {
            jobExecutionOverview = salaryTaskletService.launchPreDataAndCalcJob(period.getSalaryPlan().getId(), periodId,
                    period.getCurrentTerm().getId(), null, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1,
                    period.getCurrentTerm().getTaskExecutionSequence());
        } catch (JobExecutionAlreadyRunningException e) {
            ValidationUtils.reject(null, "数据核算任务正在执行中，不能反复执行");
        }

        ValidationUtils.rejectIfEmpty(jobExecutionOverview, null, "任务未能执行，请刷新后重试");
        PayPeriodTerm.JobExecutionIdListWrapper jobExecutionIds = new PayPeriodTerm.JobExecutionIdListWrapper(Lists.newArrayList());
        jobExecutionIds.getJobExecutionIds().add(jobExecutionOverview.getExecutionId());
        period.getCurrentTerm().setJobExecutionIds(jobExecutionIds);

        period.setState(PeriodState.CALCULATING);
        period.getCurrentTerm().setState(PeriodState.CALCULATING);
        ((SalaryTaskService) AopContext.currentProxy()).saveDataInNewTransaction(period);

        return jobExecutionOverview;
    }*/

    @Override
    public JobExecutionOverview reCalculate(Long periodId, List<String> staffIdList, boolean reloadData) {
        ActivePayPeriod period = getActivePayPeriod(periodId);
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(period.getStartOn());
        JobExecutionOverview jobExecutionOverview = null;
        try {
            if (reloadData) {
                jobExecutionOverview = salaryTaskletService.launchPreDataAndCalcJob(period.getSalaryPlan().getId(), periodId,
                        period.getCurrentTerm().getId(), staffIdList, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1,
                        period.getCurrentTerm().getTaskExecutionSequence());
            } else {
                jobExecutionOverview = salaryTaskletService.launchCalcJob(period.getSalaryPlan().getId(), periodId,
                        period.getCurrentTerm().getId(), staffIdList, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1,
                        period.getCurrentTerm().getTaskExecutionSequence());
            }
        } catch (JobExecutionAlreadyRunningException e) {
            ValidationUtils.reject(null, "数据核算任务正在执行中，不能反复执行");
        }

        ValidationUtils.rejectIfEmpty(jobExecutionOverview, null, "任务未能执行，请刷新后重试");
        PayPeriodTerm.JobExecutionIdListWrapper jobExecutionIds = period.getCurrentTerm().getJobExecutionIds();
        //staffIdList为空时，表示全部重新核算
        if (jobExecutionIds == null || reloadData) {
            jobExecutionIds = new PayPeriodTerm.JobExecutionIdListWrapper(Lists.newArrayList());
        }
        jobExecutionIds.getJobExecutionIds().add(jobExecutionOverview.getExecutionId());
        period.getCurrentTerm().setJobExecutionIds(jobExecutionIds);

        period.setState(PeriodState.CALCULATING);
        period.getCurrentTerm().setState(PeriodState.CALCULATING);
        ((SalaryTaskService) AopContext.currentProxy()).saveDataInNewTransaction(period);
        return jobExecutionOverview;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveDataInNewTransaction(ActivePayPeriod activePayPeriod) {
        payPeriodRepository.save(activePayPeriod);
    }

    @Override
    @EnableValidation
    public Map<String, SalaryFieldEntity> getPayPeriodHeaderMap(@Valid(validator = PayPeriodAuthValidator.class) Long periodId) {
        ActivePayPeriod period = getActivePayPeriod(periodId);
        List<SalaryFieldVo> payPeriodHeader = salaryPlanService.getPlanSalaryFieldsWithOutAuth(period.getSalaryPlan().getId());
        boolean isMergeTaxPlan = period.getSalaryPlan().getMergerTaxPlan() != null;
        PayPeriodTerm currentTerm = period.getCurrentTerm();
        RefServiceStatistics refServiceStatistics = currentTerm.getRefServiceStatistics();
        return convertSalaryFieldVos(payPeriodHeader,
                refServiceStatistics != null ? refServiceStatistics.getRefAttendance() : false,
                refServiceStatistics != null ? refServiceStatistics.getRefSihf() : false,
                isMergeTaxPlan);
    }

    private static final String ATTENDANCE_STATUS = "考勤状态";
    private static final String SIHF_STATUS = "社保状态";

    private Map<String, SalaryFieldEntity> convertSalaryFieldVos(List<SalaryFieldVo> salaryFieldVos, boolean isRefAttendance, boolean isRefSihf, boolean isMergeTaxPlan) {
        if (CollectionUtils.isEmpty(salaryFieldVos)) {
            return Maps.newHashMap();
        }
        Map<String, SalaryFieldEntity> result = Maps.newLinkedHashMap();
        result.put(EXCEL_FIELD_NAME, new SalaryFieldEntity(EXCEL_FIELD_NAME, SyncedBasicStaffInfo.Field.staffName, true));
        result.put(EXCEL_FIELD_MOBILE_NO, new SalaryFieldEntity(EXCEL_FIELD_MOBILE_NO, SyncedBasicStaffInfo.Field.mobileNo, true));
        if (isRefAttendance) {
            result.put(ATTENDANCE_STATUS, new SalaryFieldEntity(ATTENDANCE_STATUS, StaffPayPeriodTermSalary.Field.ATTENDANCE_STATUS, true));
        }
        if (isRefSihf) {
            result.put(SIHF_STATUS, new SalaryFieldEntity(SIHF_STATUS, StaffPayPeriodTermSalary.Field.SIHF_STATUS, true));
        }


        for (SalaryFieldVo field : salaryFieldVos) {
            SalaryFieldEntity entity = new SalaryFieldEntity();
            entity.setFieldName(field.getName());
            entity.setColumnName(field.getCode());
            entity.setSource(field.getSource());
            entity.setTaxMergeField(field.isTaxMergeField());
            entity.setReadOnly(field.getSource() != FieldSource.INPUT);
            if (isMergeTaxPlan && StringUtils.isNotEmpty(field.getValueMeta().getMergeTaxFormula())) {
                entity.setFormula(field.getValueMeta().getMergeTaxFormula());
            } else {
                entity.setFormula(field.getValueMeta().getFormula());
            }

            SalaryFieldValueType type = field.getValueMeta().getValueType();
            switch (type) {
                case NUMBER:
                    entity.setType("DOUBLE");
                    break;
                case DATE:
                case TIME:
                case DATETIME:
                    entity.setType("DATE");
                    entity.setRegexp(type.getPattern());
                    break;
                case ARRAY:
                    break;
                default:
                    entity.setType("STRING");
                    break;
            }
            result.put(entity.getFieldName(), entity);
        }
        return result;
    }

    private Page<StaffPayPeriodTermSalaryVo> getPayPeriodStaffData(String companyId, Long periodId,
                                                                   Ihr360SearchBody<SyncedBasicStaffInfo> searchBody) {
        ActivePayPeriod period = getActivePayPeriod(periodId);
        ValidationUtils.rejectIfEmpty(period.getCurrentTerm(), null, "账期的当前批次数据不存在，请刷新后重试");
        Page<BasicStaffInfoEntity> page = salaryPlanStaffService.getStaffs4ActivePayPeriod(companyId, period.getSalaryPlan().getId(),
                (Ihr360Specification<SyncedBasicStaffInfo>) searchBody.getSpecification(), searchBody.getPageable());
        List<BasicStaffInfoEntity> staffsInSalaryPlan = page.getContent();
        if (CollectionUtils.isEmpty(staffsInSalaryPlan)) {
            return new PageImpl<>(Collections.emptyList());
        }
        Map<String, BasicStaffInfoEntity> idMap = staffsInSalaryPlan.stream()
                .collect(Collectors.toMap(BasicStaffInfoEntity::getId, s -> s));
        Long termId = period.getCurrentTerm().getId();
        List<StaffPayPeriodTermSalary> existedStaffsInTerm = staffPayPeriodTermSalaryRepository
                .findByIdTermIdAndIdStaffIdIn(termId, idMap.keySet());

        Map<String, StaffPayPeriodTermSalary> existedIdMap = existedStaffsInTerm.stream().collect(Collectors.toMap(StaffPayPeriodTermSalary::getStaffId, s -> s));
        List<StaffPayPeriodTermSalaryVo> result = Lists.newArrayListWithCapacity(staffsInSalaryPlan.size());
        staffsInSalaryPlan.forEach(staff ->
                        result.add(convertToVo(termId, idMap.get(staff.getId()), existedIdMap.get(staff.getId())))
        );
        return new PageImpl<>(result, null, page.getTotalElements());
    }

    private StaffPayPeriodTermSalaryVo convertToVo(Long termId, BasicStaffInfoEntity staff, StaffPayPeriodTermSalary termSalary) {

        if (termSalary != null) {
            if (termSalary.getSummarizedData() == null) {
                termSalary.setSummarizedData(new StaffSalarySnapshot());
            }
            StaffSalarySnapshot summarizedData = termSalary.getSummarizedData();
            if (summarizedData.getData() == null) {
                summarizedData.setData(Maps.newHashMap());
            }
            StaffRefStatus attendanceStatus = termSalary.getAttendanceStatus();
            summarizedData.getData().put(StaffPayPeriodTermSalary.Field.ATTENDANCE_STATUS,
                    new DynamicCell(attendanceStatus == null ? null : attendanceStatus.getTitle()));
            StaffRefStatus sihfStatus = termSalary.getSihfStatus();
            summarizedData.getData().put(StaffPayPeriodTermSalary.Field.SIHF_STATUS,
                    new DynamicCell(sihfStatus == null ? null : sihfStatus.getTitle()));

            StaffSalarySnapshotListWrapper phasedDataWrapper = termSalary.getPhasedData();
            List<Map<String, DynamicCell>> phasedDatas = Lists.newArrayList();
            SimpleDateFormat dateFormat = new SimpleDateFormat(DateUtils.STANDARD_DATE_FORMAT_MD);
            if (phasedDataWrapper != null && CollectionUtils.isNotEmpty(phasedDataWrapper.getPhasedData())
                    && phasedDataWrapper.getPhasedData().size() > 1) {
                List<StaffSalarySnapshot> phasedData = phasedDataWrapper.getPhasedData();
                phasedData.forEach(oneData -> {
                    Map<String, DynamicCell> data = oneData.getData();
                    String dateStr = getFormatDateStr(oneData.getStartOn(), oneData.getEndOn(), dateFormat);
                    if (dateStr != null) {
                        if (CollectionUtils.isEmpty(data)) {
                            data = Maps.newHashMap();
                        }
                        //分段计薪数据中，姓名按照计算的起止日期显示
                        data.put(SyncedBasicStaffInfo.Field.staffName, new DynamicCell(dateStr));
                    }
                    phasedDatas.add(data);
                });
            }

            return new StaffPayPeriodTermSalaryVo(termSalary.getId(), staff.getStaffName(), staff.getMobileNo(),
                    summarizedData.getData(), phasedDatas);
        }
        return new StaffPayPeriodTermSalaryVo(new StaffSalaryTermId(termId, staff.getId()), staff.getStaffName(), staff.getMobileNo(), null, null);
    }

    private String getFormatDateStr(Date startOn, Date endOn, SimpleDateFormat dateFormat) {
        if (startOn == null || endOn == null) {
            return null;
        }
        return new StringBuilder().append(dateFormat.format(startOn))
                .append("-").append(dateFormat.format(endOn)).toString();
    }

    @Override
    public Page<SalaryDataEntity> getPayPeriodStaffDataCells(String companyId, Long periodId, Ihr360SearchBody<SyncedBasicStaffInfo> searchBody) {
        Page<StaffPayPeriodTermSalaryVo> page = getPayPeriodStaffData(companyId, periodId, searchBody);
        return new PageImpl<>(convertPayPeriodTermSalaryVos(page.getContent()), searchBody.getPageable(), page.getTotalElements());
    }

    private List<SalaryDataEntity> convertPayPeriodTermSalaryVos(List<StaffPayPeriodTermSalaryVo> salaryVos) {
        if (CollectionUtils.isEmpty(salaryVos)) {
            return Collections.emptyList();
        }
        List<SalaryDataEntity> result = Lists.newArrayListWithCapacity(salaryVos.size());

        salaryVos.forEach(item -> {
            SalaryDataEntity entity = new SalaryDataEntity();
            entity.setId(item.getId());
            entity.setDatas(Lists.newArrayList());
            entity.getDatas().add(new ExcelDataCellEntity(SyncedBasicStaffInfo.Field.staffName, item.getStaffName(), item.getStaffName()));
            entity.getDatas().add(new ExcelDataCellEntity(SyncedBasicStaffInfo.Field.mobileNo, item.getMobileNo(), item.getMobileNo()));
            Map<String, DynamicCell> summaryData = item.getSummaryData();
            if (summaryData != null) {
                summaryData.forEach((key, value) -> {
                    String cellValue = value != null ? value.getCellValue() : null;
                    entity.getDatas().add(new ExcelDataCellEntity(key, cellValue, cellValue));
                });
            }

            List<Map<String, DynamicCell>> phasedDatas = item.getPhasedData();
            if (CollectionUtils.isNotEmpty(phasedDatas)) {
                entity.setSubDatas(Lists.newArrayListWithCapacity(phasedDatas.size()));
                phasedDatas.forEach(data -> {
                    if (CollectionUtils.isEmpty(data)) {
                        return;
                    }
                    List<ExcelDataCellEntity> onePhased = Lists.newArrayListWithCapacity(data.size());
                    data.forEach((key, value) -> {
                        String cellValue = value != null ? value.getCellValue() : null;
                        onePhased.add(new ExcelDataCellEntity(key, cellValue, cellValue));
                    });
                    entity.getSubDatas().add(onePhased);
                });
            }
            result.add(entity);
        });
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @EnableValidation
    public void savePayPeriodSalaryData(@Valid(validator = PayPeriodTermAuthValidator.class) Long termId, List<SalaryDataEntity> salaryDataEntities) {

        ValidationUtils.rejectIfEmpty(salaryDataEntities, null, "要保存的数据不能为空");
        PayPeriodTerm currentTerm = payPeriodTermRepository.findOne(termId);

        List<StaffSalaryTermId> termIds = salaryDataEntities.stream()
                .map(s -> new StaffSalaryTermId(termId, s.getId().getStaffId())).collect(Collectors.toList());

        Iterable<StaffPayPeriodTermSalary> all = staffPayPeriodTermSalaryRepository.findAll(termIds);
        Map<StaffSalaryTermId, StaffPayPeriodTermSalary> termIdDtoMap = Maps.newHashMap();
        all.forEach(item -> termIdDtoMap.put(item.getId(), item));


        ValidationUtils.rejectIfEmpty(currentTerm, null, "当前账期批次不存在");

        List<StaffPayPeriodTermSalary> savedDtos = Lists.newArrayListWithCapacity(salaryDataEntities.size());
        for (SalaryDataEntity entity : salaryDataEntities) {
            if (CollectionUtils.isEmpty(entity.getDatas())) {
                continue;
            }
            StaffPayPeriodTermSalary dto;
            if (termIdDtoMap.containsKey(entity.getId())) {
                dto = termIdDtoMap.get(entity.getId());
            } else {
                dto = new StaffPayPeriodTermSalary();
                dto.setId(entity.getId());
                dto.setCompanyId(currentTerm.getCompanyId());
                dto.setTerm(currentTerm);
                dto.setStartOn(currentTerm.getStartOn());
                dto.setEndOn(currentTerm.getEndOn());
            }
            if (dto.getSummarizedData() == null) {
                dto.setSummarizedData(new StaffSalarySnapshot());
                dto.getSummarizedData().setStartOn(dto.getStartOn());
                dto.getSummarizedData().setEndOn(dto.getEndOn());
            }
            if (dto.getSummarizedData().getData() == null) {
                dto.getSummarizedData().setData(Maps.newHashMap());
            }
            List<ExcelDataCellEntity> datas = entity.getDatas();
            datas.forEach(data -> dto.getSummarizedData().getData().put(data.getColumnName(), new DynamicCell(data.getValue())));
            savedDtos.add(dto);
        }
        staffPayPeriodTermSalaryRepository.save(savedDtos);
        List<StaffPayPeriodTermCalcContext> salaryDataInputValues = getSalaryDataInputValue(currentTerm.getCompanyId(), termIds, salaryDataEntities);
        if (CollectionUtils.isNotEmpty(salaryDataInputValues)) {
            calcContextRepository.save(salaryDataInputValues);
        }
    }


    private List<StaffPayPeriodTermCalcContext> getSalaryDataInputValue(String companyId, List<StaffSalaryTermId> termIds, List<SalaryDataEntity> salaryDataEntities) {
        Iterable<StaffPayPeriodTermCalcContext> calcContexts = calcContextRepository.findAll(termIds);
        if (calcContexts == null || !calcContexts.iterator().hasNext()) {
            return Collections.emptyList();
        }
        Map<StaffSalaryTermId, StaffPayPeriodTermCalcContext> termIdDtoMap = Maps.newHashMap();
        calcContexts.forEach(item -> termIdDtoMap.put(item.getId(), item));

        List<StaffPayPeriodTermCalcContext> savedDtos = Lists.newArrayListWithCapacity(salaryDataEntities.size());

        for (SalaryDataEntity entity : salaryDataEntities) {
            if (CollectionUtils.isEmpty(entity.getDatas())) {
                continue;
            }
            StaffPayPeriodTermCalcContext dto;
            if (termIdDtoMap.containsKey(entity.getId())) {
                dto = termIdDtoMap.get(entity.getId());
            } else {
                dto = new StaffPayPeriodTermCalcContext();
                dto.setId(entity.getId());
                dto.setCompanyId(companyId);
            }
            if (dto.getContext() == null) {
                dto.setContext(new StaffSalaryCalcContext());
            }
            if (dto.getContext().getInputData() == null) {
                dto.getContext().setInputData(Maps.newHashMap());
            }
            List<ExcelDataCellEntity> datas = entity.getDatas();
            datas.forEach(data -> dto.getContext().getInputData().put(data.getColumnName(), new DynamicCell(data.getValue())));
            savedDtos.add(dto);
        }
        return savedDtos;
    }

//***********************************下载模板、导入、导出********************************************

    public static final String EXCEL_FIELD_NAME = "姓名";
    public static final String EXCEL_FIELD_MOBILE_NO = "手机号";

    @Autowired
    private BasicStaffInfoService staffInfoService;


    @Override
    @EnableValidation
    public void downloadTemplateBySelectedFields(@Valid(validator = PayPeriodAuthValidator.class) Long payPeriodId, List<String> fieldCodeList,
                                                 boolean containsData, ServletOutputStream outputStream) {
        ValidationUtils.rejectIfEmpty(fieldCodeList, null, "必须选择要导入的薪资字段");
        ActivePayPeriod period = getActivePayPeriod(payPeriodId);
        ValidationUtils.rejectIfEmpty(period, null, "数据不存在");
        download(period, fieldCodeList, containsData, outputStream);
    }

    private void download(ActivePayPeriod period, List<String> fieldCodeList, boolean containsData, ServletOutputStream outputStream) {
        ExportParams exportParams = new ExportParams();
        Map<String, String> headerMap = getTemplateHeader(period.getSalaryPlan().getId(), fieldCodeList);
        exportParams.setHeaderMap(headerMap);

        if (containsData) {
            List<List<Object>> rowData = getTemplateData(headerMap, period.getCompanyId(), period.getSalaryPlan().getId(), period.getCurrentTerm().getId());
            exportParams.setRowDatas(rowData);
        }

        ExcelUtil.exportExcel(exportParams, outputStream);
        try {
            outputStream.close();
        } catch (IOException e) {
            ValidationUtils.reject("", "模板下载失败,请刷新重试");
            logger.info(e.getMessage());
        }
    }

    private Map<String, String> getTemplateHeader(Long planId, List<String> fieldCodeList) {
        List<SalaryFieldVo> salaryFields = salaryPlanService.getPlanSalaryFieldsWithOutAuth(planId);
        Map<String, String> headerMap = Maps.newLinkedHashMap();
        headerMap.put(SyncedBasicStaffInfo.Field.staffName, EXCEL_FIELD_NAME);
        headerMap.put(SyncedBasicStaffInfo.Field.mobileNo, EXCEL_FIELD_MOBILE_NO);
        salaryFields.forEach(field -> {
            if (fieldCodeList.contains(field.getCode())) {
                headerMap.put(field.getCode(), field.getName());
            }
        });
        return headerMap;
    }

    private List<List<Object>> getTemplateData(Map<String, String> headerMap, String companyId, Long planId, Long termId) {
        Page<BasicStaffInfoEntity> page = salaryPlanStaffService.getStaffs4ActivePayPeriod(companyId, planId, null, new PageRequest(0, Integer.MAX_VALUE));
        List<BasicStaffInfoEntity> staffs = page.getContent();

        Map<String, StaffPayPeriodTermSalary> staffIdMap =
                staffPayPeriodTermSalaryService.loadPayperiodSalaryOfStaffsWithMapResult(
                        termId, staffs.stream().map(BasicStaffInfoEntity::getId).collect(Collectors.toSet())
                );

        Set<String> headers = Sets.newLinkedHashSet(headerMap.keySet());
        headers.remove(SyncedBasicStaffInfo.Field.staffName);
        headers.remove(SyncedBasicStaffInfo.Field.mobileNo);

        List<List<Object>> result = Lists.newArrayListWithCapacity(staffs.size());
        staffs.forEach(staff -> {
            List<Object> row = Lists.newArrayList();
            row.add(staff.getStaffName());
            row.add(staff.getMobileNo());
            if (staffIdMap.containsKey(staff.getId()) && staffIdMap.get(staff.getId()).getSummarizedData() != null) {
                Map<String, DynamicCell> data = staffIdMap.get(staff.getId()).getSummarizedData().getData();
                headers.forEach(header -> {
                    DynamicCell dynamicCell = data.get(header);
                    row.add(dynamicCell != null ? dynamicCell.getCellValue() : null);
                });
            }
            result.add(row);
        });
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @EnableValidation
    public void importExcel(@Valid(validator = PayPeriodAuthValidator.class) Long payPeriodId, ImportRequest importRequest) {
        ActivePayPeriod activePayPeriod = getActivePayPeriod(payPeriodId);
        ValidationUtils.rejectIfEmpty(activePayPeriod, null, "数据不存在");

        //******************************************保存员工台账数据********************************************/
        List<StaffPayPeriodTermSalary> importedStaffLedgers = null;
        try {
            importedStaffLedgers = importRequest.getConvertResultList();
        } catch (Exception e) {
            logger.error(e.toString());
            ValidationUtils.reject(null, "正确数据导入失败");
        }

        if (CollectionUtils.isNotEmpty(importedStaffLedgers)) {
            staffPayPeriodTermSalaryRepository.save(importedStaffLedgers);
            salaryPlanStaffService.dummyAddStaff2Plan(activePayPeriod.getSalaryPlan().getId(),
                    importedStaffLedgers.stream().map(item -> item.getId().getStaffId()).collect(Collectors.toList()));

            //将输入项的值保存到context中
            List<SalaryDataEntity> salaryDataEntities = getInputData(activePayPeriod.getSalaryPlan().getId(), importedStaffLedgers);
            List<StaffPayPeriodTermCalcContext> salaryDataInputValues = getSalaryDataInputValue(activePayPeriod.getCompanyId(),
                    importedStaffLedgers.stream().map(StaffPayPeriodTermSalary::getId).collect(Collectors.toList()),
                    salaryDataEntities);
            if (CollectionUtils.isNotEmpty(salaryDataInputValues)) {
                calcContextRepository.save(salaryDataInputValues);
            }
        }
    }

    private List<SalaryDataEntity> getInputData(Long planId, List<StaffPayPeriodTermSalary> importedStaffLedgers) {
        List<SalaryDataEntity> result = Lists.newArrayListWithCapacity(importedStaffLedgers.size());

        List<SalaryFieldVo> planSalaryFields = salaryPlanService.getPlanSalaryFields(planId);
        Set<String> inputFieldCodes = planSalaryFields.stream().filter(field -> field.getSource() == FieldSource.INPUT)
                .map(SalaryFieldVo::getCode).collect(Collectors.toSet());
        importedStaffLedgers.forEach(ledger -> {
            StaffSalarySnapshot summarizedData = ledger.getSummarizedData();
            if (summarizedData == null || CollectionUtils.isEmpty(summarizedData.getData())) {
                return;
            }
            List<ExcelDataCellEntity> inputItems = Lists.newArrayList();
            Map<String, DynamicCell> data = summarizedData.getData();
            data.forEach((code, value) -> {
                if (inputFieldCodes.contains(code)) {
                    inputItems.add(new ExcelDataCellEntity(code, value.getCellValue()));
                }
            });
            if (CollectionUtils.isNotEmpty(inputItems)) {
                result.add(new SalaryDataEntity(ledger.getId(), inputItems));
            }
        });
        return result;
    }

    @Override
    public CustomSpreadSheetValidateResult validateExcelData(Long payPeriodId, SpreadSheetValidatorRequest request) {
        ActivePayPeriod activePayPeriod = getActivePayPeriod(payPeriodId);

        CustomSpreadSheetValidateResult result = new CustomSpreadSheetValidateResult();
        List<SpreadSheetRow> excelDatas = request.getSpreadSheetDatas();
        List<StaffPayPeriodTermSalary> termSalaryList = validateExcel(activePayPeriod, excelDatas, result);
        try {
            result.setConvertResult(SpreadSheetConvertResultUtil.ObjectToByte(termSalaryList));
        } catch (IOException e) {
            logger.error(e.toString());
            ValidationUtils.reject(null, "可导入的正确数据保存失败");
        }
        return result;
    }


    /**
     * 校验导入的数据
     *
     * @param payPeriod
     * @param excelDatas
     * @param result
     * @return
     */
    private List<StaffPayPeriodTermSalary> validateExcel(ActivePayPeriod payPeriod,
                                                         List<SpreadSheetRow> excelDatas,
                                                         CustomSpreadSheetValidateResult result) {
        List<SalaryFieldVo> validSalaryFields = salaryPlanService.getPlanSalaryFields(payPeriod.getSalaryPlan().getId());
        ValidationUtils.rejectIfEmpty(validSalaryFields, null, "当前薪资方案中无可用的薪资表项目");

        Map<String, SalaryFieldVo> nameSalaryFieldMap = validSalaryFields.stream().collect(
                Collectors.toMap(SalaryFieldVo::getName, self -> self));

        //校验表头
        validateHeader(excelDatas.get(0), nameSalaryFieldMap, result);

        Map<String, SpreadSheetRow> staffIdRowMap = excelDatas.stream().collect(Collectors.toMap(SpreadSheetRow::getStaffId, s -> s));

        return convertExcelRows2Dtos(payPeriod, staffIdRowMap, result);
    }

    private void validateHeader(SpreadSheetRow excelRow,
                                Map<String, SalaryFieldVo> nameSalaryFieldMap,
                                CustomSpreadSheetValidateResult result) {
        List<SpreadSheetCell> cells = excelRow.getCells();
        Map<String, SpreadSheetCell> nameMap = cells.stream().collect(Collectors.toMap(SpreadSheetCell::getHeaderName, s -> s));
        nameMap.forEach((name, cell) -> {
            if (EXCEL_FIELD_NAME.equals(name)
                    || EXCEL_FIELD_MOBILE_NO.equals(name)
                    || ExcelDefaultConfig.COMMON_SPECIFICATION_ROWNUM.equals(name)) {
                return;
            }
            if (!nameSalaryFieldMap.containsKey(name)) {
                addRowLog(result, null, cell.getColNum(), String.format("【%s】列不在薪资账期的可导入范围内", name));
            }
        });
    }

    private void addRowLog(CustomSpreadSheetValidateResult result, Integer rowNum, Integer colNum, String log) {
        if (result.getErrors() == null) {
            result.setErrors(Lists.newArrayList());
        }
        SpreadSheetValidatorError error = new SpreadSheetValidatorError();
        error.setRowNum(rowNum);
        error.setColNum(colNum);
        error.setMsg(log);
        result.getErrors().add(error);
    }

    private List<StaffPayPeriodTermSalary> convertExcelRows2Dtos(ActivePayPeriod payPeriod,
                                                                 Map<String, SpreadSheetRow> validRows,
                                                                 CustomSpreadSheetValidateResult result) {
        if (CollectionUtils.isEmpty(validRows)) {
            return Collections.emptyList();
        }
        List<SalaryFieldVo> validSalaryFields = salaryPlanService.getPlanSalaryFields(payPeriod.getSalaryPlan().getId());
        ValidationUtils.rejectIfEmpty(validSalaryFields, null, "当前薪资方案中无可用的薪资表项目");
        Map<String, SalaryFieldVo> nameFieldMap = validSalaryFields.stream().collect(
                Collectors.toMap(SalaryFieldVo::getName, s -> s));

        List<StaffPayPeriodTermSalary> allSaved = Lists.newArrayListWithCapacity(validRows.size());
        String companyId = payPeriod.getCompanyId();
        PayPeriodTerm currentTerm = payPeriod.getCurrentTerm();
        Long termId = currentTerm.getId();
        Date startOn = currentTerm.getStartOn();
        Date endOn = currentTerm.getEndOn();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(startOn);
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH) + 1;

        Map<String, StaffPayPeriodTermSalary> existedTermSalaryMap =
                staffPayPeriodTermSalaryService.loadPayperiodSalaryOfStaffsWithMapResult(termId, validRows.keySet());

        validRows.forEach((staffId, row) -> {
            StaffPayPeriodTermSalary dto;
            if (existedTermSalaryMap.containsKey(staffId)) {
                dto = existedTermSalaryMap.get(staffId);
            } else {
                dto = new StaffPayPeriodTermSalary();
                dto.setId(new StaffSalaryTermId(termId, staffId));
                dto.setCompanyId(companyId);
                dto.setStartOn(startOn);
                dto.setEndOn(endOn);
                dto.setTerm(currentTerm);
                dto.setYear(year);
                dto.setMonth(month);
            }
            if (dto.getSummarizedData() == null) {
                StaffSalarySnapshot summarizedData = new StaffSalarySnapshot();
                summarizedData.setStartOn(startOn);
                summarizedData.setEndOn(endOn);
                summarizedData.setData(Maps.newHashMap());
                dto.setSummarizedData(summarizedData);
            }
            Map<String, DynamicCell> summaryData = convertExcelRow2SummaryData(nameFieldMap, row, result);
            summaryData.forEach((key, value) -> dto.getSummarizedData().getData().put(key, value));
            allSaved.add(dto);
        });
        return allSaved;
    }

    private Map<String, DynamicCell> convertExcelRow2SummaryData(Map<String, SalaryFieldVo> nameFieldMap,
                                                                 SpreadSheetRow row,
                                                                 CustomSpreadSheetValidateResult result) {
        Map<String, DynamicCell> summaryData = Maps.newHashMap();
        List<SpreadSheetCell> cells = row.getCells();
        Map<String, SpreadSheetCell> nameMap = cells.stream()
                .collect(Collectors.toMap(SpreadSheetCell::getHeaderName, s -> s));
        nameMap.forEach((name, value) -> {
            SalaryFieldVo fieldVo = nameFieldMap.get(name);
            if (fieldVo == null) {
                return;
            }
            if (value == null || value.getCellValue() == null || StringUtils.isBlank(value.getCellValue().toString())) {
                summaryData.put(fieldVo.getCode(), new DynamicCell(null));
                return;
            }
            String valueStr = value.getCellValue().toString();
            Integer rowNum = value.getRowNum();
            Integer colNum = value.getColNum();
            if (fieldVo.getValueMeta().getValueType() == SalaryFieldValueType.NUMBER) {
                double doubleValue = 0;
                try {
                    doubleValue = Double.parseDouble(valueStr);
                } catch (NumberFormatException e) {
                    addRowLog(result, rowNum, colNum, String.format("【%s】值不是有效的数字", name));
                }
                if (doubleValue < fieldVo.getValueMeta().getMinValue()) {
                    addRowLog(result, rowNum, colNum, String.format("【%s】值不能小于【%d】", name, fieldVo.getValueMeta().getMinValue()));
                    return;
                }
                if (doubleValue > fieldVo.getValueMeta().getMaxValue()) {
                    addRowLog(result, rowNum, colNum, String.format("【%s】值不能大于【%d】", name, fieldVo.getValueMeta().getMinValue()));
                    return;
                }
                summaryData.put(fieldVo.getCode(), new DynamicCell(doubleValue));
            } else {
                summaryData.put(fieldVo.getCode(), new DynamicCell(valueStr));
            }
        });
        return summaryData;
    }

    @Override
    public void exportExcel(Long payPeriodId, ServletOutputStream outputStream) {
        ActivePayPeriod period = getActivePayPeriod(payPeriodId);
        List<SalaryFieldVo> salaryFields = salaryPlanService.getPlanSalaryFields(period.getSalaryPlan().getId());
        List<String> fieldCodeList = salaryFields.stream().map(SalaryFieldVo::getCode).collect(Collectors.toList());
        download(period, fieldCodeList, true, outputStream);
    }

    @Override
    public List<PayrollApprovalResponse> queryApprovalPayPeriod(String companyId, List<String> applicationIdList) {
        List<PayrollApprovalResponse> list = Lists.newArrayList();
        List<ActivePayPeriod> periodList = payPeriodRepository.findByCompanyIdAndApplicationIdIn(companyId, applicationIdList);
        if (CollectionUtils.isNotEmpty(periodList)) {
            periodList.forEach(activePayPeriod -> {
                PayrollApprovalResponse response = new PayrollApprovalResponse();
                response.setId(activePayPeriod.getId());
                response.setName(activePayPeriod.getSalaryPlan().getName());
                response.setPayDate(DateUtils.formatDateStr(activePayPeriod.getStartOn(), DateUtils.STANDARD_DATE_FORMAT_YM1));
                response.setApplicationId(activePayPeriod.getApplicationId());
                response.setClosed(false);
                list.add(response);
            });
        }
        //查询是否有没有查询到的applicationId,去historyPayPeriod中查询剩余的账期列表
        List<String> existApplicationId = periodList.stream().map(ActivePayPeriod::getApplicationId).collect(Collectors.toList());
        List<String> closedIdList = Lists.newArrayList();
        applicationIdList.forEach(id -> {
            if (!existApplicationId.contains(id)) {
                closedIdList.add(id);
            }
        });
        if (CollectionUtils.isEmpty(closedIdList)) {
            return list;
        }
        List<HistoryPayPeriod> historyPayPeriodList = historyPayPeriodRepository.findByCompanyIdAndApplicationIdIn(companyId, closedIdList);
        historyPayPeriodList.forEach(historyPayPeriod -> {
            PayrollApprovalResponse response = new PayrollApprovalResponse();
            response.setId(historyPayPeriod.getId());
            response.setName(historyPayPeriod.getSalaryPlan().getName());
            response.setPayDate(DateUtils.formatDateStr(historyPayPeriod.getStartOn(), DateUtils.STANDARD_DATE_FORMAT_YM1));
            response.setApplicationId(historyPayPeriod.getApplicationId());
            response.setClosed(true);
            list.add(response);
        });
        return list;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateApprovalState(String companyId, String applicationId, PeriodState state) {
        ActivePayPeriod activePayPeriod = payPeriodRepository.findByCompanyIdAndApplicationId(companyId, applicationId);
        if (activePayPeriod == null) {
            throw new Ihr360Exception(null, "该账期记录不存在");
        }
        //关账时同步历史报表数据
        if (PeriodState.CLOSED.equals(state)) {
            checkPayPeriodIsExist(activePayPeriod.getSalaryPlan().getId(), activePayPeriod);
            historyPayPeriodService.syncPayPeriod(companyId, activePayPeriod.getId());
        } else {//同步状态
            activePayPeriod.setState(state);
            payPeriodRepository.save(activePayPeriod);
        }
    }

    @Override
    public void saveApprovalApplicationId(String companyId, ApplicationApprovalEntity applicationApprovalEntity, Long id) {
        String applicationId = applicationApprovalEntity.getApplicationId();
        List<String> approvalStaffList = applicationApprovalEntity.getApprovalStaffList();
        if (StringUtils.isEmpty(applicationId)) {
            throw new Ihr360Exception(null, "审批记录id不可为空");
        }
        ActivePayPeriod activePayPeriod = payPeriodRepository.findOne(id);
        if (activePayPeriod == null) {
            throw new Ihr360Exception(null, "该账期记录不存在");
        }
        activePayPeriod.setState(PeriodState.APPROVING);
        activePayPeriod.setApplicationId(applicationId);
        payPeriodRepository.save(activePayPeriod);
        //审批时手动为审批人添加数据权限，web-staff端调用接口使用
        if (CollectionUtils.isNotEmpty(approvalStaffList)) {
            Long salaryPlanId = activePayPeriod.getSalaryPlan().getId();
            Long payrollConfigId = activePayPeriod.getSalaryPlan().getPayrollConfig().getId();
            authUserResourceService.addUserResource(companyId, ActivePayPeriod.class.getSimpleName(), approvalStaffList, id.toString());
            authUserResourceService.addUserResource(companyId, SalaryPlan.class.getSimpleName(), approvalStaffList, salaryPlanId.toString());
            authUserResourceService.addUserResource(companyId, PayrollConfig.class.getSimpleName(), approvalStaffList, payrollConfigId.toString());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void closeActivePeriod(Long id, String companyId) {
        ActivePayPeriod payPeriod = getActivePayPeriod(id);
        checkPayPeriodIsExist(payPeriod.getSalaryPlan().getId(), payPeriod);
        historyPayPeriodService.syncPayPeriod(companyId, payPeriod.getId());
    }


    private void checkPayPeriodIsExist(Long salaryPlanId, ActivePayPeriod period) {
        int year = period.getYear();
        int month = period.getMonth();
        HistoryPayPeriod historyPayPeriod = historyPayPeriodRepository.findBySalaryPlanSalaryPlanIdAndYearAndMonth(salaryPlanId, year, month);
        if (historyPayPeriod != null) {
            throw new Ihr360Exception(null, "薪资方案该月已生成过历史账期，无法重新核算");
        }
    }
}
